package services;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import models.Report;
import models.Resource;
import models.ResourceType;
import models.Setting;

/**
 * SynchronizationService It loads all projects from robot framework directory and checks agains console database. If the console db does not have entry of the project, it will create one. If the console db does have the project entry, it will update lastModified date If there are files insides the project directory, it will consider as test suites
 * 
 * It also synchronize configurations and resources
 * 
 * @author june
 * 
 */
public class SynchronizationService {
	
	private final SettingService settingService = SettingService.getService();
	private final ProjectService projectService = ProjectService.getService();
	private final ConfigurationService configService = ConfigurationService.getService();
	private final TestSuiteService testSuiteService = TestSuiteService.getService();
	private final TestCaseService testCaseService = TestCaseService.getService();
	private final ReportService reportService = ReportService.getService();
	
	private static final String PATH_SEPARATOR = "/";
	private static final String CONFIG_FILE_PREFIX = "variables_";
	private static final String CONFIG_FILE_EXTENSION = ".py";
	
	private static SynchronizationService service;
	
	// Singleton pattern
	private SynchronizationService() {
	}
	
	public static SynchronizationService getService() {
		if (service == null) {
			service = new SynchronizationService();
			service.synchronizeRemoteResources();
		}
		return service;
	}
	
	public void synchronizeRemoteResources() {
		synchronizeConfigurations(settingService.getSetting(Setting.REMOTE_CONFIG_HOME).getStringValue());
		synchronizeProjects(settingService.getSetting(Setting.REMOTE_PROJECT_HOME).getStringValue());
	}
	
	public List<Report> synchronizeReports(String reportRepositoryPath) {
		List<Report> reports = new ArrayList<Report>();
		String[] projectNames = getSubDirectories(new File(reportRepositoryPath));
		if (projectNames == null || projectNames.length == 0) {
			return reports; // No report found
		}
		
		for (String projectName : projectNames) {
			reports.addAll(synchronizeProjectReport(reportRepositoryPath, projectName));
		}
		return reports;
	}
	
	private List<Report> synchronizeProjectReport(String reportRepositoryPath, String projectName) {
		String[] reportDirectoryNames = getSubDirectories(new File(reportRepositoryPath + PATH_SEPARATOR + projectName));
		List<Report> reports = new ArrayList<Report>();
		for (String reportDirectoryName : reportDirectoryNames) {
			Report report = reportService.createReportModel(projectName, reportDirectoryName, reportRepositoryPath);
			if (report != null) {
				reports.add(report);
			}
		}
		return reports;
	}
	
	public void synchronizeConfigurations(String configRepositoryPath) {
		String[] configNames = getFiles(new File(configRepositoryPath));
		if (configNames == null || configNames.length == 0) {
			return; // No config found
		}
		for (String configName : configNames) {
			if (configName.startsWith(CONFIG_FILE_PREFIX) && configName.endsWith(CONFIG_FILE_EXTENSION)) {
				synchronizeConfig(configRepositoryPath, configName);
			}
		}
	}
	
	private void synchronizeConfig(String configRepositoryPath, String configName) {
		Resource config = configService.getConfig(configName);
		if (config == null) {
			addConfig(configName);
		} else {
			updateConfig(config);
		}
	}
	
	private void updateConfig(Resource config) {
		configService.saveRemoteConfig(config);
	}
	
	private void addConfig(String configName) {
		Resource config = new Resource();
		config.setName(configName);
		config.setDescription("Sychronized config");
		config.setRemote(true);
		configService.saveRemoteConfig(config);
	}
	
	public void synchronizeProjects(String projectRepositoryPath) {
		String[] projectNames = getSubDirectories(new File(projectRepositoryPath));
		if (projectNames == null || projectNames.length == 0) {
			return; // No projects found
		}
		for (String projectName : projectNames) {
			synchronizeProject(projectRepositoryPath, projectName);
		}
	}
	
	private void synchronizeProject(String projectRepositoryPath, String projectName) {
		Resource project = projectService.getProject(projectName);
		if (project == null) {
			createProject(projectRepositoryPath, projectName);
		} else {
			updateProject(projectRepositoryPath, project);
		}
	}
	
	private void updateProject(String projectRepositoryPath, Resource project) {
		projectService.saveProject(project);
		
		updateTestSuites(projectRepositoryPath, project);
	}
	
	private void updateTestSuites(String projectRepositoryPath, Resource project) {
		File projectDirectory = new File(projectRepositoryPath, project.getName());
		String[] testSuiteFiles = getFiles(projectDirectory);
		if (testSuiteFiles == null || testSuiteFiles.length == 0) {
			deleteTestSuites(project);
		}
		for (String testSuite : testSuiteFiles) {
			updateTestSuite(project, new File(projectDirectory, testSuite));
		}
	}
	
	private void deleteTestSuites(Resource project) {
		testSuiteService.deleteTestSuites(project.getId());
	}
	
	private void updateTestSuite(Resource project, File testSuiteFile) {
		Resource testSuite = testSuiteService.getTestSuite(testSuiteFile.getName());
		if (testSuite == null) {
			createTestSuite(project, testSuiteFile);
		} else {
			testSuiteService.saveTestSuite(project.getId(), testSuite);
		}
	}
	
	private void createProject(String projectRepositoryPath, String projectName) {
		Resource project = new Resource(projectName);
		project.setDescription("Sychronized project");
		project.setRemote(true);
		project = projectService.saveProject(project);
		
		createTestSuites(project, projectRepositoryPath);
	}
	
	private void createTestSuites(Resource project, String projectRepositoryPath) {
		File projectDirectory = new File(projectRepositoryPath, project.getName());
		String[] testSuiteFiles = getFiles(projectDirectory);
		if (testSuiteFiles == null || testSuiteFiles.length == 0) {
			return; // No test suites
		}
		for (String testSuite : testSuiteFiles) {
			createTestSuite(project, new File(projectDirectory, testSuite));
		}
	}
	
	private void createTestSuite(Resource project, File testSuiteFile) {
		Resource testSuite = new Resource(testSuiteFile.getName());
		testSuite.setDescription("Sychronized test suite");
		testSuite.setRemote(true);
		testSuiteService.saveTestSuite(project.getId(), testSuite);
		
		createTestCases(project, testSuite, testSuiteFile);
	}
	
	private void createTestCases(Resource project, Resource testSuite, File file) {
		if (testSuite != null && testSuite.getId() > 0 && file != null && file.exists() && file.isFile()) {
			try {
				Document document = Jsoup.parse(file, "UTF-8");
				Elements testCaseTables = document.select("table#testcase"), testCaseLinks;
				for (Element testCaseTable : testCaseTables) {
					testCaseLinks = testCaseTable.select("a[name^=test_]");
					for (Element testCaseLink : testCaseLinks) {
						createTestCase(testSuite, testCaseLink);
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	private void createTestCase(Resource testSuite, Element testCaseElement) {
		Resource testCase = new Resource(testCaseElement.text());
		testCase.setRemote(true);
		testCase.setType(ResourceType.TestCase);
		testCase.setCreateDate(new Date());
		testCase.setLastModified(new Date());
		testCaseService.saveTestCase(testSuite.getId(), testCase);
	}
	
	private String[] getSubDirectories(File parentDirectory) {
		String[] directories = parentDirectory.list(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return new File(dir, name).isDirectory();
			}
		});
		return directories;
	}
	
	private String[] getFiles(File parentDirectory) {
		String[] files = parentDirectory.list(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return new File(dir, name).isFile();
			}
		});
		return files;
	}
	
}
